#include "LireSorts.h"

#include "ATL.h"

#include "lib.h"

#include "ElementDOM.h"

//#include "debugstreams.h"

#include <boost/assign/list_of.hpp>

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

#include <boost/foreach.hpp>

using namespace std;
using namespace boost;
using namespace boost::lambda;
using namespace boost::assign;

/**
 * Instance utilisée comme "objet statique", vu qu'apparemment un
 * objet statique n'a pas ses champs statiques définis correctement
 * lors de son initialisation.
 */
LireSorts listeSortsXML;

/**
 * Fonction qui permet d'initialiser l'objet \c listeSortsXML à la
 * demande. Si ledit objet a déjà été initialisé, la fonction ne fait
 * rien.
 *
 * @return l'adresse listeSortsXML, ou 0 si un problème apparaît lors
 * de la création de l'objet.
 */
const LireSorts * initLectureSortXML()
{
  if ( listeSortsXML.initialisationOK() )
    return &listeSortsXML;

  StatusCode sc( listeSortsXML . init() );

  if ( ! sc )
    {
      sc.afficher();
      FlotMessage err( ERR );
      err << "Impossible de récupérer la liste d'armes" << endmsg;
      return 0;
    }

  return &listeSortsXML;
}


bool LireSorts::niveauxLus( false );

const vector< string > LireSorts::NomsNiveaux =
  list_of( "LireSorts::elementaire" );

DebugLevelType LireSorts::elementaire( INFO );

/**
 * Constructeur par défaut, construit le parseur et lit le niveau de
 * sortie si aucune autre instance ne l'a déjà fait.
 */
LireSorts::LireSorts()
  : ADDObjetBase( "LireSorts" ), parser( "Joueur0/Donnees/ListeSorts.xml" ),
    InitialisationOK( false )
{
  if ( ! niveauxLus )
    {
      lireNiveaux( this, NomsNiveaux );
      niveauxLus = true;
    }
}

/**
 * Le destructeur n'a rien de spécial à faire.
 */
LireSorts::~LireSorts()
{}

/**
 * Méthode qui termine l'initialisation de la classe. Réalise en
 * premier lieu la lecture du fichier XML, puis la création de la
 * structure DOM du document. Ces deux étapes peuvent renvoyer un
 * StatusCode en cas d'échec.
 *
 * Ensuite la liste des sorts est créée.
 *
 * @return StatusCode.
 */
StatusCode LireSorts::init()
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "LireSorts::init -> début" << endmsg;
  StatusCode sc( parser . chargerFichier() );

  if ( ! sc )
    {
      error() << sc.message() << endmsg;
      func() << "LireSorts::init -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return sc;
    }

  sc = parser . lireDocument();

  if ( ! sc )
    {
      error() << sc.message() << endmsg;
      func() << "LireSorts::init -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return sc;
    }
  
  const ElementDOM * liste = parser . premierEnfant();

  vector< const ElementDOM * > ecoles = trouverEcoles( liste ), niveauSort,
    sorts;

  const ElementDOM * el, * el2, * el3;

  unsigned ecole, niveau, nombreNiv, nombreSorts;

  string nomEcole, nomSort;

  vector< vector< SortsEcoles > > uniqueEcole;
  vector< SortsEcoles > uniqueNiveau;

  BOOST_FOREACH( el, ecoles )
    {
      nomEcole = trouverNom( el );

      if ( ! lireString( el -> attribut( "numero" ), ecole ) )
	{
	  error() << "Ne peut pas lire le numéro de l'école pour "
		  << nomEcole << endmsg;
	  el -> afficheAttributs( error() );
	  error() << endmsg;
	  continue;
	}
      if ( ! lireString( el -> attribut( "nombre" ), nombreNiv ) )
	{
	  error() << "Ne peut pas lire le nombre de niveau de l'école pour "
		  << nomEcole << endmsg;
	  el -> afficheAttributs( error() );
	  error() << endmsg;
	  continue;
	}
      
      linfo()  << nomEcole << ", numéro = " << ecole
	       << " (" << el -> balise() << ')' << endmsg;
      
      niveauSort = trouverNiveaux( el );
      
      if ( niveauSort . size() != nombreNiv )
	{
	  warning() << "Attention, le nombre de niveau de sorts récupéré est "
		    << "de " << niveauSort . size()
		    << " alors qu'on en attend " << nombreNiv << endmsg;
	}
      
      BOOST_FOREACH( el2, niveauSort )
	{
	  uniqueNiveau.clear();

	  if ( ! lireString( el2 -> attribut( "nombre" ),
			     nombreSorts ) )
	    {
	      error() << "Ne peut pas lire le nombre de sorts pour "
		      << nomEcole << endmsg;
	      el2 -> afficheAttributs( error() );
	      error() << endmsg;
	      continue;
	    }
	  
	  el3 = parser . premierEnfant( el2 );
	  if ( el3 -> balise() != "Niveau" )
	    {
	      error() << "La balise attendue est \'Niveau\' mais on a \'"
		      << el3 -> balise() << "\'" << endmsg;
	      continue;
	    }
	  else if ( ! lireString( el3 -> donnees(), niveau ) )
	    {
	      error() << "Impossible de lire le niveau de sorts pour "
		      << nomEcole << endmsg;
	      continue;
	    }

	  //el3 = parser . prochainFrere( el3 );

	  sorts = trouverSorts( el2 );

	  if ( sorts . size() != nombreSorts )
	    {
	      warning() << "Attention, le nombre  de sorts récupéré est "
			<< "de " << sorts . size()
			<< " alors qu'on en attend " << nombreSorts << endmsg;
	    }

	  BOOST_FOREACH( el3, sorts )
	    {
	      uniqueNiveau . push_back( lireSort( el3, ecole, niveau  ) );
	    }
	  uniqueEcole . push_back( uniqueNiveau );
	}
      if ( ListeSorts.size() < ecole )
	ListeSorts.resize( ecole + 1 );
      ListeSorts[ ecole ].assign( uniqueEcole.begin(),
				  uniqueEcole.end() );
      uniqueEcole.clear();
    }

  InitialisationOK = true;

  func() << "LireSorts::init -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return sc;
}

/**
 * Permet de récupérer un pointeur sur la liste des sorts.
 *
 * @return l'addresse de ListeSorts.
 */
const vector< vector< vector< SortsEcoles > > > * LireSorts::listeSorts() const
{
  return &ListeSorts;
}

/**
 * Permet de récupérer la liste de sorts d'une école bien précise. Un
 * test est effectué pour savoir si l'école existe ou non.
 *
 * @return l'adresse de la liste pour l'école voulue, ou 0 si l'école
 * n'existe pas.
 */
const vector< vector< SortsEcoles > > *
LireSorts::listeSorts( const unsigned& ecole ) const
{
  if ( ecole >= ListeSorts.size() )
    {
      warning() << "Tentative d'accéder à une école inexistante" << endmsg;
      return 0;
    }
  else
    return &ListeSorts[ ecole ];
}

/**
 * Permet de récupérer la liste de sorts d'une école bien précise pour
 * un niveau donne. Un test est effectué pour savoir si l'école et le
 * niveau existent ou non.
 *
 * @return l'adresse de la liste pour l'école et le niveau voulus, ou
 * 0 si l'école n'existe pas.
 */
const vector< SortsEcoles > *
LireSorts::listeSorts( const unsigned& ecole, const unsigned & niveau ) const
{
  if ( ecole >= ListeSorts.size() )
    {
      warning() << "Tentative d'accéder à une école inexistante" << endmsg;
      return 0;
    }
  else if ( niveau >= ListeSorts[ ecole ] . size() )
    {
      warning() << "Tentative d'accéder à un niveau inexistante" << endmsg;
      return 0;
    }
  else
    return &ListeSorts[ ecole ][ niveau ];
}

/**
 *
 */
const bool& LireSorts::initialisationOK() const
{
  return InitialisationOK;
}

/**
 * Gestion du niveau de sortie des fonctions.
 *
 * @param level le niveau.
 */

void LireSorts::setLevel( const unsigned & level, const unsigned& )
{
  elementaire = static_cast< DebugLevelType >( level );
}

/**
 * Méthode permettant de trouver les ElementDOM étant des écoles.
 *
 * @param elDom pointeur sur l'ElementDOM dont on veut les
 * descendants de type "Ecole".
 *
 * @greturn la liste des enfants de \c elDom.
 */
vector< const ElementDOM * >
LireSorts::trouverEcoles( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverEcoles -> début" << endmsg;
  vector< const ElementDOM * > retour;

  if ( elDom -> balise() == "Liste" )
    {
      retour . assign( elDom -> enfants() -> begin(),
		       elDom -> enfants() -> end() );

      vector< const ElementDOM * >::const_iterator
        debut( retour.begin() ), fin( retour.end() );
      
      fin = remove_if( retour.begin(), retour.end(),
                       bind< string >( &ElementDOM::balise, _1 ) == "Nom" );

      retour . assign( debut, fin );
    }
  else
    {
      error() << "La balise attendue était \'Liste\', reçu \'"
	      << elDom -> balise() << "\'" << endmsg;
      func() << "trouverEcoles -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return retour;
    }

  info() << "Trouvé " << retour . size() << " écoles" << endmsg;
  
  func() << "trouverEcoles -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return retour;
}

/**
 * Méthode permettant de récupérer la liste des sorts d'un groupe
 * donné.
 *
 * @param elDom pointeur sur l'ElementDOM "Sorts".
 *
 * @return la liste des sorts, qui est vide si \c elDom est incorrect.
 */
vector< const ElementDOM * > LireSorts::trouverNiveaux( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverNiveaux -> début" << endmsg;
  vector< const ElementDOM * > retour;

  if ( elDom -> balise() != "Ecole" )
    {
      error() << "Tentative de trouver la liste de sorts, mais la balise est \'"
	      << elDom -> balise() << "\' au lieu de \'Sorts\'" << endmsg;
      func() << "trouverNiveaux -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return retour;
    }
  else
    {
      retour . assign( elDom -> enfants() -> begin(),
		       elDom -> enfants() -> end() );

      vector< const ElementDOM * >::const_iterator
        debut( retour.begin() ), fin( retour.end() );
      
      fin = remove_if( retour.begin(), retour.end(),
                       bind< string >( &ElementDOM::balise, _1 ) == "Nom" );

      retour . assign( debut, fin );
    }
      
  func() << "trouverNiveaux -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return retour;
}

/**
 * Méthode permettant de récupérer la liste des sorts d'un groupe
 * donné.
 *
 * @param elDom pointeur sur l'ElementDOM "Sorts".
 *
 * @return la liste des sorts, qui est vide si \c elDom est incorrect.
 */
vector< const ElementDOM * > LireSorts::trouverSorts( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverSorts -> début" << endmsg;
  vector< const ElementDOM * > retour;

  if ( elDom -> balise() != "Sorts" )
    {
      error() << "Tentative de trouver la liste de sorts, mais la balise est \'"
	      << elDom -> balise() << "\' au lieu de \'Sorts\'" << endmsg;
      func() << "trouverSorts -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return retour;
    }
  else
    {
      retour . assign( elDom -> enfants() -> begin(),
		       elDom -> enfants() -> end() );

      vector< const ElementDOM * >::const_iterator
        debut( retour.begin() ), fin( retour.end() );
      
      fin = remove_if( retour.begin(), retour.end(),
                       bind< string >( &ElementDOM::balise, _1 ) == "Niveau" );

      retour . assign( debut, fin );
    }
      
  func() << "trouverSorts -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return retour;
}

/**
 * Méthode permettant de récupérer le nom du sort voulu.
 *
 * @param elDom pointeur sur un ElementDOM "Sort".
 *
 * @return le nom du sort, ou une chaîne vide si \c elDom est
 * incorrect.
 */
string LireSorts::trouverNom( const ElementDOM * elDom )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "trouverNom -> début" << endmsg;
  string nomBalise( elDom -> balise() );

  if ( ! ( nomBalise == "Sort" || nomBalise == "Ecole" ) )
    {
      error() << "Tentative de trouver le nom d'une école ou d'un sort"
	      << " alors que la balise est \'" << nomBalise << "\'" << endmsg;
      func() << "trouverNom -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return "";
    }

  const ElementDOM * fils = parser . premierEnfant( elDom );

  if ( fils == 0 )
    {
      error() << "Pas de balise trouvée, alors qu'on attendait \'Nom\'" << endmsg;
      return "";
    }

  if ( fils -> balise() != "Nom" )
    {
      error() << "On attend la balise \'Niveau\', mais reçu \'"
	      << fils -> balise() << "\'" << endmsg;
      return "";
    }

  func() << "trouverNom -> fin" << endmsg;
  return fils -> donnees();
}

/**
 * Méthode qui construit l'instance de SortsEcoles à partir d'une
 * balise "Sort". Tous les champs nécessaires sont initialisé. La
 * structure du document XML n'est pas vraiment vérifiée, vu que la
 * validation est faite par SchemaSort.xsd.
 *
 * @param elDom pointeur sur un ElementDOM "Sort".
 * @param ecole numéro de l'école courante.
 * @param niveau numéro du sort courant.
 *
 * @return une instance de SortsEcoles, anonyme en cas d'erreur.
 */
SortsEcoles LireSorts::lireSort( const ElementDOM * elDom, const unsigned & ecole, const unsigned & niveau )
{
  /*Debug::*/empileNiveau( &elementaire );
  func() << "lireSort -> début" << endmsg;
  string nomBalise( elDom -> balise() );


  if ( nomBalise != "Sort" )
    {
      error() << "Tentative de lire un sort, mais la balise est \'" << nomBalise << "\'" << endmsg;
      func() << "lireSort -> fin" << endmsg;
      /*Debug::*/depileNiveau();
      return SortsEcoles();
    }
  
  string nom( "" );
  unsigned numero( 0 );

  const ElementDOM * courant;

  courant = parser . premierEnfant( elDom );
  nom = courant -> donnees();
  courant = parser . prochainFrere( courant );
  if ( ! lireString( courant -> donnees(), numero ) )
    {
      error() << "Impossible de lire le numéro du sort" << endmsg;
      return SortsEcoles();
    }

  func() << "lireSort -> fin" << endmsg;
  /*Debug::*/depileNiveau();
  return SortsEcoles( nom, niveau, ecole, numero );
}
